﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using A = DocumentFormat.OpenXml.Drawing;
using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
using System.IO;



namespace intelliEssay.Core
{
    /// <summary>
    /// This class is used when a document exception is found
    /// </summary>
    public class EssayException
    {
        public override string ToString()
        {
            return string.Format("{0} at {1}", Description, faultyElement.InnerText.PadRight(40, ' ').Substring(0, 40));
        }

        public EssayException(Task task)
        {
            this.task = task;
            this.faultyElement = task.documentStructure.NoWherePointer;
        }

        internal EssayException()
        {
        }
        /// <summary>
        /// Instantiate an instance of EssayException type.
        /// </summary>
        /// <param name="et">The type of this exception. choices are in enum ExceptionType</param>
        /// <param name="desc">The DISPLAYED description for this exception</param>
        /// <param name="FaultyLocation">The location where this exception occured, must be of type Run or Paragraph</param>
        /// <param name="task">The asscoicated task of this exception</param>
        /// <param name="iserror">True if this exception is treated as an error rather than a warning</param>
        public EssayException(ExceptionType et, string desc, OpenXmlElement FaultyLocation, Task task, bool iserror)
        {

            if (FaultyLocation.GetType() != typeof(Paragraph) && FaultyLocation.GetType() != typeof(Run))
            {
                throw new ApplicationException("FaultyLocation must be of type Paragraph or Run");
            }
            if (FaultyLocation.InnerText.Trim() == "" && FaultyLocation != task.documentStructure.NoWherePointer)
            {
                throw new ApplicationException("This FaultyLocation does not contain any text, thus, the check of which should be skipped");
            }
            exceptionType = et;
            Description = desc;
            faultyElement = FaultyLocation;
            this.task = task;
            isError = iserror;
        }
        private void InsertAPicture()
        {
            MainDocumentPart mainPart = task.TaskObjectRoot.MainDocumentPart;

            ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Jpeg);

            using (FileStream stream = new FileStream(intelliEssay.Core.EnvironmentVariables.Constants.ErrorSignPath, FileMode.Open))
            {
                imagePart.FeedData(stream);
            }

            AddImageToBody(mainPart.GetIdOfPart(imagePart));

        }
        private void AddImageToBody(string relationshipId)
        {
            //Define the reference of the image.
            var element =
                 new Drawing(
                     new DW.Inline(
                         new DW.Extent() { Cx = 990000L, Cy = 792000L },
                         new DW.EffectExtent()
                         {
                             LeftEdge = 0L,
                             TopEdge = 0L,
                             RightEdge = 0L,
                             BottomEdge = 0L
                         },
                         new DW.DocProperties()
                         {
                             Id = (UInt32Value)1U,
                             Name = "Picture 1"
                         },
                         new DW.NonVisualGraphicFrameDrawingProperties(
                             new A.GraphicFrameLocks() { NoChangeAspect = true }),
                         new A.Graphic(
                             new A.GraphicData(
                                 new PIC.Picture(
                                     new PIC.NonVisualPictureProperties(
                                         new PIC.NonVisualDrawingProperties()
                                         {
                                             Id = (UInt32Value)0U,
                                             Name = "New Bitmap Image.jpg"
                                         },
                                         new PIC.NonVisualPictureDrawingProperties()),
                                     new PIC.BlipFill(
                                         new A.Blip(
                                             new A.BlipExtensionList(
                                                 new A.BlipExtension()
                                                 {
                                                     Uri =
                                                       "{28A0092B-C50C-407E-A947-70E740481C1C}"
                                                 })
                                         )
                                         {
                                             Embed = relationshipId,
                                             CompressionState =
                                             A.BlipCompressionValues.Print
                                         },
                                         new A.Stretch(
                                             new A.FillRectangle())),
                                     new PIC.ShapeProperties(
                                         new A.Transform2D(
                                             new A.Offset() { X = 0L, Y = 0L },
                                             new A.Extents() { Cx = 990000L, Cy = 792000L }),
                                         new A.PresetGeometry(
                                             new A.AdjustValueList()
                                         ) { Preset = A.ShapeTypeValues.Rectangle }))
                             ) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" })
                     )
                     {
                         DistanceFromTop = (UInt32Value)0U,
                         DistanceFromBottom = (UInt32Value)0U,
                         DistanceFromLeft = (UInt32Value)0U,
                         DistanceFromRight = (UInt32Value)0U,
                         EditId = "50D07946"
                     });

            //Append the reference to body, the element should be in a Run.
            //wordDoc.MainDocumentPart.Document.Body.AppendChild(new Paragraph(new Run(element)));
            // return;
            Stack<OpenXmlElement> prevSiblings = new Stack<OpenXmlElement>();
            Queue<OpenXmlElement> nextSiblings = new Queue<OpenXmlElement>();
            List<OpenXmlElement> allChildren = faultyElement.Parent.ChildElements.ToList<OpenXmlElement>();
            int count = allChildren.Count;
            bool prevTonext = false;
            for (int i = 0; i < count; i++)
            {

                if (allChildren[i] == faultyElement)
                {
                    prevTonext = true;
                    continue;
                }
                if (!prevTonext)
                {
                    prevSiblings.Push(allChildren[i]);
                    faultyElement.Parent.RemoveChild(allChildren[i]);
                }
                if (prevTonext)
                {
                    nextSiblings.Enqueue(allChildren[i]);
                    faultyElement.Parent.RemoveChild(allChildren[i]);
                }
            }
            while (prevSiblings.Count > 0)
            {
                faultyElement.Parent.PrependChild(prevSiblings.Pop());
            }
            faultyElement.Parent.AppendChild(new Run(element));
            while (nextSiblings.Count > 0)
            {
                faultyElement.Parent.AppendChild(nextSiblings.Dequeue());
            }
            //       faultyElement.Parent.AppendChild(new Run(element));
        }
        public string DetailedInformation { get; set; }
        /// <summary>
        /// The type of the exception
        /// <see cref="intelliEssay.Core.ExceptionType"/>
        /// </summary>
        public ExceptionType exceptionType { get; set; }
        /// <summary>
        /// The task that this exception belongs to
        /// </summary>
        public Task task { get; set; }
        /// <summary>
        /// Determines if this exception should be regarded as an error which eventually will be drawn in UI.
        /// </summary>
        public bool isError { get; set; }
        /// <summary>
        /// The element where the fault happened
        /// </summary>
        public OpenXmlElement faultyElement { get; set; }
        /// <summary>
        /// The description of the exception. It has default value regarding the type of the exception.
        /// </summary>
        public string Description { get; set; }
    }
    /// <summary>
    /// 论文错误类型。
    /// </summary>
    [Flags]
    public enum ExceptionType
    {
        /// <summary>
        /// 关键元素缺少
        /// </summary>
        Element,
        /// <summary>
        /// 文档内容顺序错误，如，关键词出现在摘要之前
        /// </summary>
        ElementSequence,
        /// <summary>
        /// 对齐错误，如未居中
        /// </summary>
        Alignment,
        /// <summary>
        /// 字体错误，如颜色
        /// </summary>
        Font,
        /// <summary>
        /// 签名错误，如未签名
        /// </summary>
        Signature,
        /// <summary>
        /// 格式错误，如参考文献标号没有用中括号括住
        /// </summary>
        Formatting,
        /// <summary>
        /// 符号错误，如双引号和引号交叉
        /// </summary>
        Syntactic,
        /// <summary>
        /// 引用错误，如交叉引用错误
        /// </summary>
        Reference,
        /// <summary>
        /// 参考文献错误，如引用了无效的文档或错误的页码
        /// </summary>
        Bibliography,
        /// <summary>
        /// 表格图片叠放错误，如互相覆盖
        /// </summary>
        TablePicturePlacemnt,
        /// <summary>
        /// 公式错误，指公式内容错误，而不是公式格式错误。
        /// </summary>
        Equation,
        Numbering
    }
    /// <summary>
    /// 这是一个包装论文异常的类，方便快速存取
    /// </summary> 

    public class ChainedException
    {
        public LinkedList<EssayException> ChainedExceptions = new LinkedList<EssayException>();
        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            if (ChainedExceptions.Count != 0)
            {
                stringBuilder.Append(ChainedExceptions.First.Value.ToString().TrimEnd());
                var current = ChainedExceptions.First.Next;
                while (current != null)
                {
                    stringBuilder.Append(current.Value.faultyElement.InnerText);
                    current = current.Next;
                }
            }
            return stringBuilder.ToString();
        }
    }

    public class DisplayReadyExceptionCollection : List<ChainedException>
    {
    }

    public class EssayExceptionCollection : List<EssayException>
    {


        class RunSequenceComparer : IComparer<Run>
        {
            Dictionary<Paragraph, int> ParaSequence;
            Dictionary<Paragraph, Dictionary<Run, int>> RunSequencesList;
            public RunSequenceComparer(WordprocessingDocument wordProcDoc)
            {
                RunSequencesList = new Dictionary<Paragraph, Dictionary<Run, int>>();
                ParaSequence = new Dictionary<Paragraph, int>();
                for (int i = 0; i < wordProcDoc.MainDocumentPart.Document.Body.ChildElements.Count; i++)
                {
                    if (wordProcDoc.MainDocumentPart.Document.Body.ChildElements[i].GetType() == typeof(Paragraph))
                    {
                        ParaSequence.Add(wordProcDoc.MainDocumentPart.Document.Body.ChildElements[i] as Paragraph, i);
                    }
                }
            }

            public int Compare(Run x, Run y)
            {
                OpenXmlElement paraX = x.Parent;
                OpenXmlElement paraY = y.Parent;

                while (paraX.GetType() != typeof(Paragraph))
                {
                    paraX = paraX.Parent;
                }

                while (paraY.GetType() != typeof(Paragraph))
                {
                    paraY = paraY.Parent;
                }

                if (ParaSequence[paraX as Paragraph] - ParaSequence[paraY as Paragraph] != 0)
                {
                    return ParaSequence[(paraX as Paragraph)] - ParaSequence[(paraY as Paragraph)];
                }
                else
                {
                    var para = paraX as Paragraph;
                    if (!RunSequencesList.ContainsKey(para))
                    {
                        para.Elements<Run>();
                        Dictionary<Run, int> currentParaInformation = new Dictionary<Run, int>();
                        var currentParaRuns = para.Elements<Run>();
                        var allRunsInParagraphs = currentParaRuns.Union<Run>(para.Elements<Hyperlink>().SelectMany<Hyperlink, Run>(hyperlink =>
                             {
                                 return hyperlink.Elements<Run>();
                             })).ToArray();
                        for (int i = 0; i < allRunsInParagraphs.Length; i++)
                        {
                            currentParaInformation.Add(allRunsInParagraphs[i], i);
                        }
                        RunSequencesList.Add(para, currentParaInformation);
                    }
                    return RunSequencesList[para][x] - RunSequencesList[para][y];
                }
            }
        }
        DisplayReadyExceptionCollection merged;

        /// <summary>
        /// Merge the similar essay exceptions and open xml elements in adjacency
        /// </summary>
        /// <returns></returns>
        public DisplayReadyExceptionCollection Merge(bool forceRemerge = false)
        {
            if (this.Count == 0) return null;
            if (forceRemerge == false)
            {
                if (merged != null) return merged;
            }
            Task currentTask = this[0].task;
            Dictionary<Paragraph, int> paraSequence = new Dictionary<Paragraph, int>();
            Dictionary<OpenXmlElement, EssayException> mergeDictionary = new Dictionary<OpenXmlElement, EssayException>();
            for (int i = 0; i < base.Count; i++)
            {
                if (mergeDictionary.ContainsKey(base[i].faultyElement))
                {
                    mergeDictionary[base[i].faultyElement].Description += " " + base[i].Description;
                    mergeDictionary[base[i].faultyElement].exceptionType |= base[i].exceptionType;
                }
                else
                {
                    mergeDictionary.Add(base[i].faultyElement, base[i]);
                }
            }
            var mergedPargraphs = mergeDictionary.Values.Where<EssayException>(essay =>
                {
                    return essay.faultyElement.GetType() == typeof(Paragraph);
                });

            var orderedExceptionSequences = mergeDictionary.Values.Where<EssayException>(ee =>
            {
                return ee.faultyElement.GetType() == typeof(Run);
            }).OrderBy(new Func<EssayException, Run>(essayException =>
                {
                    return essayException.faultyElement as Run;
                }), new RunSequenceComparer(currentTask.TaskObjectRoot)).ToList();

            DisplayReadyExceptionCollection chainedExceptions = new DisplayReadyExceptionCollection();
            ChainedException chainedException = new ChainedException();
            if (orderedExceptionSequences.Count != 0)
            {
                chainedException.ChainedExceptions.AddLast(orderedExceptionSequences[0]);
                for (int i = 0; i < orderedExceptionSequences.Count - 1; i++)
                {
                    if ((orderedExceptionSequences[i + 1].faultyElement as Run).PreviousSibling() != (orderedExceptionSequences[i].faultyElement as Run)
                        || (orderedExceptionSequences[i + 1].exceptionType != orderedExceptionSequences[i].exceptionType))
                    {
                        chainedExceptions.Add(chainedException);
                        chainedException = new ChainedException();
                    }
                    chainedException.ChainedExceptions.AddLast(orderedExceptionSequences[i + 1]);
                }
                //chainedExceptions.Add(chainedException);
            }
            foreach (var ee in mergedPargraphs)
            {
                chainedException = new ChainedException();
                chainedException.ChainedExceptions.AddLast(ee);
                chainedExceptions.Add(chainedException);
            }

            return merged = chainedExceptions;
        }
    }
}
